#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

static int major;
static struct class *led_class;

/* registers */
// RCC_PLL4CR 地址：0x50000000 + 0x894
static volatile unsigned int *RCC_PLL4CR;

// RCC_MP_AHB4ENSETR 地址：0x50000000 + 0xA28
static volatile unsigned int *RCC_MP_AHB4ENSETR;

// GPIOA_MODER 地址：0x50002000 + 0x00
static volatile unsigned int *GPIOA_MODER;

// GPIOA_BSRR 地址：0x50002000 + 0x18
static volatile unsigned int *GPIOA_BSRR;


static ssize_t led_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) {
    char val;
    /* copy_from_user : get data from app */
    copy_from_user(&val, buf, 1);

    /* to set gpio register: out 1/0 */
    if (val) 
    {
        /* set gpio to let led on */
        *GPIOA_BSRR = (1<<26);
    } 
    else 
    {
        /* set gpio to let led off */
        *GPIOA_BSRR = (1<<10);
    }

    return 1;
}

static int led_open (struct inode *node, struct file *file) {
    /* enable PLL4, it is clock source for all gpio */
    *RCC_PLL4CR |= (1<<0);
    while ((*RCC_PLL4CR | (1<<1)) == 0);

    /* enable gpioA */
	*RCC_MP_AHB4ENSETR |= (1<<0);
	
	/*
	 * configure gpa10 as gpio
	 * configure gpio as output 
	 */
	*GPIOA_MODER &= ~(3<<20);
	*GPIOA_MODER |= (1<<20);
    
    return 0;
}

static const struct file_operations led_fops = {
	.owner		= THIS_MODULE,
    .open       = led_drv_open,
    .write      = led_drv_write,
};

static int __init led_init(void) {
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

    major = register_chrdev(0, "my_led", &led_fops);
    
    /* ioremap */
    // RCC_PLL4CR 地址：0x50000000 + 0x894
    RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);
    // RCC_MP_AHB4ENSETR 地址：0x50000000 + 0xA28
    RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
    // GPIOA_MODER 地址：0x50002000 + 0x00
    GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
    // GPIOA_BSRR 地址：0x50002000 + 0x18
    GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);

    led_class = create_class(THIS_MODULE, "myled");
    device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");     /* /dev/myled */

    return 0;
}

static void __exit led_exit(void) {
    
    iounmap(RCC_PLL4CR);
    iounmap(RCC_MP_AHB4ENSETR);
    iounmap(GPIOA_MODER);
    iounmap(GPIOA_BSRR);

    device_destory(led_class, MKDEV(major, 0));
    class_destory(led_class);

    unregister_chrdev(major, "my_led");
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");